// 2015年版权所有etcd作者
// 
// 根据Apache许可证2.0版（以下简称“许可证”）获得许可；
// 除非遵守许可证，否则不得使用此文件。
// 您可以通过
// 
// www.apache.org/licenses/LICENSE-2.0 
// 
// 除非适用法律要求或书面同意，否则根据许可证分发的软件
// 按“原样”分发，
// 无任何明示或暗示的保证或条件。
// 请参阅许可证，了解管理许可的特定语言和
// 许可证下的限制。

package httpproxy

import (
	"encoding/json"
	"net/http"
	"strings"
	"time"

	"go.uber.org/zap"
	"golang.org/x/net/http2"
)

const (
	// defaultmaxidleconspherhost表示代理和每个成员之间维护的默认最大空闲连接
	// 计数。我们将其设置为128到
	// 让代理长期平稳地处理128个并发请求。
	// 如果并发请求数大于此值，
	// 代理在处理
	// 增量中的每个请求时需要创建一个新连接，这是不好的，因为创建会消耗资源，
	// 可能会占用临时端口。
	DefaultMaxIdleConnsPerHost = 128
)

// GetProxyURLs是一个函数，它应该将当前的URL集返回给
// 应该代理哪些客户端请求。代理处理程序将定期查询此函数
// 以刷新可用
// 后端集。
type GetProxyURLs func() []string

// NewHandler创建一个新的HTTP处理程序，侦听给定的传输，
// 它将代理对etcd集群的请求。
// 处理程序将定期更新其集群视图。
func NewHandler(lg *zap.Logger, t *http.Transport, urlsFunc GetProxyURLs, failureWait time.Duration, refreshInterval time.Duration) http.Handler {
	if lg == nil {
		lg = zap.NewNop()
	}
	if t.TLSClientConfig != nil {
		// 启用http2，请参阅第5033期。
		err := http2.ConfigureTransport(t)
		if err != nil {
			lg.Info("Error enabling Transport HTTP/2 support", zap.Error(err))
		}
	}

	p := &reverseProxy{
		lg:        lg,
		director:  newDirector(lg, urlsFunc, failureWait, refreshInterval),
		transport: t,
	}

	mux := http.NewServeMux()
	mux.Handle("/", p)
	mux.HandleFunc("/v2/config/local/proxy", p.configHandler)

	return mux
}

// NewReadonlyHandler将给定的HTTP处理程序包装为仅允许GET请求
func NewReadonlyHandler(hdlr http.Handler) http.Handler {
	readonly := readonlyHandlerFunc(hdlr)
	return http.HandlerFunc(readonly)
}

func readonlyHandlerFunc(next http.Handler) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		if req.Method != "GET" {
			w.WriteHeader(http.StatusNotImplemented)
			return
		}

		next.ServeHTTP(w, req)
	}
}

func (p *reverseProxy) configHandler(w http.ResponseWriter, r *http.Request) {
	if !allowMethod(w, r.Method, "GET") {
		return
	}

	eps := p.director.endpoints()
	epstr := make([]string, len(eps))
	for i, e := range eps {
		epstr[i] = e.URL.String()
	}

	proxyConfig := struct {
		Endpoints []string `json:"endpoints"`
	}{
		Endpoints: epstr,
	}

	json.NewEncoder(w).Encode(proxyConfig)
}

// allowMethod验证给定的方法是否为允许的方法之一，
// 如果不是，则将错误写入w。返回一个布尔值，指示
// 是否允许该方法。
func allowMethod(w http.ResponseWriter, m string, ms ...string) bool {
	for _, meth := range ms {
		if m == meth {
			return true
		}
	}
	w.Header().Set("Allow", strings.Join(ms, ","))
	http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
	return false
}
